/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "mkfs_command.h"

#include <unistd.h>
#include <cinttypes>
#include <sstream>
#include <unordered_map>
#include <string_view>

#include "config.h"
#include "hmfs_utils.h"
#include "hmfs_data.h"
#include "hmfs_quota.h"
#include "hmfs_common.h"
#include "hmfs_encoding.h"

namespace OHOS {
namespace Hmfs {

constexpr uint32_t DEFAULT_OPTION_SET_HARMONY = 1;

MkfsCmdParser::MkfsCmdParser() : CmdParser(cmdPara_)
{
    cmdPara_.deviceList.resize(1);

    using namespace std::placeholders;
    RegCmdOption('a', true, std::bind(&MkfsCmdParser::ProcessHeapBasedAlloc, this, _1));
    RegCmdOption('c', true, std::bind(&MkfsCmdParser::ProcessDeviceList, this, _1));
    RegCmdOption('C', true, std::bind(&MkfsCmdParser::ProcessCasefolding, this, _1));
    RegCmdOption('d', true, std::bind(&MkfsCmdParser::ProcessDebugLevel, this, _1));
    RegCmdOption('e', true, std::bind(&MkfsCmdParser::ProcessColdFileExt, this, _1));
    RegCmdOption('E', true, std::bind(&MkfsCmdParser::ProcessHotFileExt, this, _1));
    RegCmdOption('f', false, std::bind(&MkfsCmdParser::ProcessForceOverwrite, this, _1));
    RegCmdOption('g', true, std::bind(&MkfsCmdParser::ProcessDefaultOptionSet, this, _1));
    RegCmdOption('h', false, std::bind(&MkfsCmdParser::ProcessHelp, this, _1));
    RegCmdOption('i', false, std::bind(&MkfsCmdParser::ProcessLargeNatBitmap, this, _1));
    RegCmdOption('l', true, std::bind(&MkfsCmdParser::ProcessVolumeLabel, this, _1));
    RegCmdOption('m', false, std::bind(&MkfsCmdParser::ProcessZonedMode, this, _1));
    RegCmdOption('o', true, std::bind(&MkfsCmdParser::ProcessOverProvision, this, _1));
    RegCmdOption('O', true, std::bind(&MkfsCmdParser::ProcessFeatures, this, _1));
    RegCmdOption('q', false, std::bind(&MkfsCmdParser::ProcessQuietMode, this, _1));
    RegCmdOption('r', false, std::bind(&MkfsCmdParser::ProcessSrandSeed, this, _1));
    RegCmdOption('R', true, std::bind(&MkfsCmdParser::ProcessRootOwner, this, _1));
    RegCmdOption('s', true, std::bind(&MkfsCmdParser::ProcessSegsPerSection, this, _1));
    RegCmdOption('S', true, std::bind(&MkfsCmdParser::ProcessSparseMode, this, _1));
    RegCmdOption('t', true, std::bind(&MkfsCmdParser::ProcessDiscardPolicy, this, _1));
    RegCmdOption('T', true, std::bind(&MkfsCmdParser::ProcessTimestamp, this, _1));
    RegCmdOption('U', true, std::bind(&MkfsCmdParser::ProcessUuid, this, _1));
    RegCmdOption('V', false, std::bind(&MkfsCmdParser::ShowVersion, this, _1));
    RegCmdOption('w', true, std::bind(&MkfsCmdParser::ProcessWantedSectorSize, this, _1));
    RegCmdOption('z', true, std::bind(&MkfsCmdParser::ProcessSectionsPerZone, this, _1));

    RegisterSingleton(this);
}

ArgParseResult MkfsCmdParser::ProcessHeapBasedAlloc(const std::string& argValue)
{
    auto value = atoi(argValue.c_str());
    cmdPara_.heapBasedAllocation = (value != 0);
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ShowVersion(const std::string& argValue)
{
    (void)argValue;
    HMFS_PRINT("mkfs.hmfs %s (%s)", HMFS_TOOLS_VERSION, HMFS_TOOLS_DATE);
    return ArgParseResult::FINISH;
}

ArgParseResult MkfsCmdParser::ProcessDeviceList(const std::string& argValue)
{
    if (cmdPara_.deviceList.size() >= MAX_DEVICE_COUNT) {
        HMFS_ERROR("Too many devices");
        return ArgParseResult::ERROR;
    }

    if (argValue.length() > MAX_DEVICE_PATH_LEN) {
        HMFS_ERROR("device path should be less than %u characters", MAX_DEVICE_PATH_LEN);
        return ArgParseResult::ERROR;
    }

    cmdPara_.deviceList.emplace_back(argValue);
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessColdFileExt(const std::string& argValue)
{
    std::vector<std::string> extList = HmfsCommon::GetInstance().SplitStringList(argValue, ',');
    for (auto& ext : extList) {
        if (ext.length() >= EXTENSION_LEN_MAX) {
            HMFS_INFO("Extension name (%s) is too long", ext.c_str());
            continue;
        }
        cmdPara_.coldExtList.emplace_back(ext);
    }
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessHotFileExt(const std::string& argValue)
{
    std::vector<std::string> extList = HmfsCommon::GetInstance().SplitStringList(argValue, ',');
    for (auto& ext : extList) {
        if (ext.length() >= EXTENSION_LEN_MAX) {
            HMFS_INFO("Extension name (%s) is too long", ext.c_str());
            continue;
        }
        cmdPara_.hotExtList.emplace_back(ext);
    }
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessForceOverwrite(const std::string& argValue)
{
    (void)argValue;
    cmdPara_.forceOverwrite = true;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessDefaultOptionSet(const std::string& argValue)
{
    std::unordered_map<std::string, uint32_t> optionSetMap = {
        {"harmonyos", DEFAULT_OPTION_SET_HARMONY},
    };

    auto it = optionSetMap.find(argValue);
    if (it == optionSetMap.end()) {
        HMFS_INFO("Invalid option set: %s", argValue.c_str());
    }
    cmdPara_.defaultOptionSet = it->second;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessHelp(const std::string& argValue)
{
    (void)argValue;
    ShowCmdUsage();
    return ArgParseResult::FINISH;
}

ArgParseResult MkfsCmdParser::ProcessVolumeLabel(const std::string& argValue)
{
    if (argValue.length() > VOLUME_NAME_MAX_LEN) {
        HMFS_ERROR("Volume Label should be less than %u characters.", VOLUME_NAME_MAX_LEN);
        return ArgParseResult::ERROR;
    }
    cmdPara_.volume = argValue;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessZonedMode(const std::string& argValue)
{
    (void)argValue;
    cmdPara_.zonedMode = true;
    cmdPara_.features |= HMFS_FEATURE_BLKZONED;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessQuietMode(const std::string& argValue)
{
    (void)argValue;
    cmdPara_.debugLevel = -1;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessSrandSeed(const std::string& argValue)
{
    (void)argValue;
    cmdPara_.fakeSeed = true;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessRootOwner(const std::string& argValue)
{
    std::vector<std::string> extList = HmfsCommon::GetInstance().SplitStringList(argValue, ':');
    if (extList.size() != 2) {
        return ArgParseResult::ERROR;
    }

    cmdPara_.rootUid = atoi(extList[0].c_str());
    cmdPara_.rootGid = atoi(extList[1].c_str());
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessSegsPerSection(const std::string& argValue)
{
    int32_t value = atoi(argValue.c_str());
    if (value <= 0) {
        HMFS_ERROR("Invalid argument");
        return ArgParseResult::ERROR;
    }
    cmdPara_.segsPerSection = value;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessSparseMode(const std::string& argValue)
{
    int64_t value = atoll(argValue.c_str());
    if (value <= 0) {
        HMFS_ERROR("Invalid argument");
        return ArgParseResult::ERROR;
    }
    cmdPara_.deviceSize = value & (~((uint64_t)(HMFS_BLOCK_SIZE - 1)));
    cmdPara_.sparseMode = true;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessDiscardPolicy(const std::string& argValue)
{
    int32_t value = atoi(argValue.c_str());
    cmdPara_.trim = (value != 0);
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessTimestamp(const std::string& argValue)
{
    cmdPara_.timeStamp = strtoul(optarg, nullptr, 0);
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessUuid(const std::string& argValue)
{
    cmdPara_.uuid = argValue;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessWantedSectorSize(const std::string& argValue)
{
    int32_t value = atoi(argValue.c_str());
    if (value <= 0) {
        HMFS_ERROR("Invalid argument");
        return ArgParseResult::ERROR;
    }
    cmdPara_.wantedSectorSize = value;
    return ArgParseResult::OK;
}

ArgParseResult MkfsCmdParser::ProcessSectionsPerZone(const std::string& argValue)
{
    int32_t value = atoi(argValue.c_str());
    if (value <= 0) {
        HMFS_ERROR("Invalid argument");
        return ArgParseResult::ERROR;
    }
    cmdPara_.sectionsPerZone = value;
    return ArgParseResult::OK;
}

int32_t MkfsCmdParser::Parse(int32_t argc, char *argv[])
{
    cmdPara_.func = MKFS;
    if (ParseOption(argc, argv)) {
        return -1;
    }

    if (optind >= argc) {
        HMFS_ERROR("Device not specified");
        ShowCmdUsage();
        return -1;
    }
    cmdPara_.deviceList[0] = argv[optind];

    if ((optind + 1) < argc) {
        if (cmdPara_.deviceList.size() > 1) {
            HMFS_ERROR("Not support custom size on multi-devices");
            ShowCmdUsage();
            return -1;
        }
        cmdPara_.wantedSectorCount = atoll(argv[optind + 1]);
    }

    ConfigOptionPacket();

    ConfigDefaultOption();

    if (!CheckOptions()) {
        return -1;
    }

    ShowCmdInfo();
    return 0;
}

void MkfsCmdParser::ConfigOptionPacket(void)
{
    switch (cmdPara_.defaultOptionSet) {
        case DEFAULT_OPTION_SET_HARMONY:
            /* -d1 -f -w 4096 -R 0:0 */
            cmdPara_.debugLevel = 1;
            cmdPara_.forceOverwrite = true;
            cmdPara_.wantedSectorSize = 4096;
            cmdPara_.rootUid = 0;
            cmdPara_.rootGid = 0;

            /* RO doesn't need any other features */
            if (cmdPara_.features & HMFS_FEATURE_RO) {
                break;
            }

            /* -O encrypt -O project_quota,extra_attr,{quota} -O verity */
            cmdPara_.features |= HMFS_FEATURE_ENCRYPT;
            cmdPara_.features |= HMFS_FEATURE_PRJQUOTA;
            cmdPara_.features |= HMFS_FEATURE_EXTRA_ATTR;
            cmdPara_.features |= HMFS_FEATURE_VERITY;
            if (!HmfsCommon::GetInstance().KernelVersionOver(4, 14)) {
                cmdPara_.features |= HMFS_FEATURE_QUOTA_INO;
            }
            break;
        default:
            break;
    }
}

void MkfsCmdParser::ConfigDefaultOption(void)
{
    /* RO doesn't need any other features */
    if ((cmdPara_.features & HMFS_FEATURE_RO) && (cmdPara_.defaultOptionSet == DEFAULT_OPTION_SET_HARMONY)) {
        return;
    }

#ifdef CONF_CASEFOLD
    c.encoding = HMFS_ENC_UTF8_12_1;
    cmdPara_.features |= HMFS_FEATURE_CASEFOLD;
#endif
#ifdef CONF_PROJID
    cmdPara_.features |= HMFS_FEATURE_QUOTA_INO;
    cmdPara_.features |= HMFS_FEATURE_PRJQUOTA;
    cmdPara_.features |= HMFS_FEATURE_EXTRA_ATTR;
#endif

    if (cmdPara_.features & HMFS_FEATURE_QUOTA_INO) {
        cmdPara_.quotaBits = QUOTA_USR_BIT | QUOTA_GRP_BIT;
    }

    if (cmdPara_.features & HMFS_FEATURE_PRJQUOTA) {
        cmdPara_.features |= HMFS_FEATURE_QUOTA_INO;
        cmdPara_.quotaBits |= QUOTA_PRJ_BIT;
    }
}

bool MkfsCmdParser::CheckOptions(void)
{

    if (!(cmdPara_.features & HMFS_FEATURE_EXTRA_ATTR)) {
        if (cmdPara_.features & HMFS_FEATURE_PRJQUOTA) {
            HMFS_ERROR("project quota feature should always be enabled with extra attr feature");
            return false;
        }
        if (cmdPara_.features & HMFS_FEATURE_INODE_CHKSUM) {
            HMFS_ERROR("inode checksum feature should always be enabled with extra attr feature");
            return false;
        }
        if (cmdPara_.features & HMFS_FEATURE_FLEXIBLE_INLINE_XATTR) {
            HMFS_ERROR("flexible inline xattr feature should always be enabled with extra attr feature");
            return false;
        }
        if (cmdPara_.features & HMFS_FEATURE_INODE_CRTIME) {
            HMFS_ERROR("inode crtime feature should always be enabled with extra attr feature");
            return false;
        }
        if (cmdPara_.features & HMFS_FEATURE_COMPRESSION) {
            HMFS_ERROR("compression feature should always be enabled with extra attr feature");
            return false;
        }
    }

    if (cfgPara_.zonedMode && !cfgPara_.trim) {
        HMFS_ERROR("Trim is required for zoned block devices.");
        return false;
    }

    return true;
}

void MkfsCmdParser::ShowCmdUsage()
{
    HMFS_PRINT("\nUsage: mkfs.hmfs [options] device [sectors]");
    HMFS_PRINT("[options]:");
    HMFS_PRINT("  -a heap-based allocation [default:0]");
    HMFS_PRINT("  -c device1[,device2,...] up to 7 additional devices, except meta device");
    HMFS_PRINT("  -d debug level [default:0]");
    HMFS_PRINT("  -e [cold file ext list] e.g. \"mp3,gif,mov\"");
    HMFS_PRINT("  -E [hot file ext list] e.g. \"db\"");
    HMFS_PRINT("  -f force overwrite of the existing filesystem");
    HMFS_PRINT("  -g add default options");
    HMFS_PRINT("  -i extended node bitmap, node ratio is 20%% by default");
    HMFS_PRINT("  -l label");
    HMFS_PRINT("  -U uuid");
    HMFS_PRINT("  -m support zoned block device [default:0]");
    HMFS_PRINT("  -o overprovision percentage [default:auto]");
    HMFS_PRINT("  -O feature1[,feature2,...] e.g. \"encrypt\"");
    HMFS_PRINT("  -C [encoding[:flag1,...]] Support casefolding with optional flags");
    HMFS_PRINT("  -q quiet mode");
    HMFS_PRINT("  -r set checkpointing seed (srand()) to 0");
    HMFS_PRINT("  -R root_owner [default: 0:0]");
    HMFS_PRINT("  -s # of segments per section [default:1]");
    HMFS_PRINT("  -S sparse mode");
    HMFS_PRINT("  -t 0: nodiscard, 1: discard [default:1]");
    HMFS_PRINT("  -T timestamps");
    HMFS_PRINT("  -w wanted sector size");
    HMFS_PRINT("  -z # of sections per zone [default:1]");
    HMFS_PRINT("  -V print the version number and exit");
    HMFS_PRINT("sectors: number of sectors [default: determined by device size]");
}

void MkfsCmdParser::ShowCmdInfo()
{
    HMFS_PRINT("    HMFS-tools: mkfs.hmfs Ver: %s (%s)", HMFS_TOOLS_VERSION, HMFS_TOOLS_DATE);

    if (cfgPara_.heapBasedAllocation) {
        HMFS_INFO("Enable heap-based policy");
    }

    HMFS_INFO("Debug level = %d", cfgPara_.debugLevel);
    if (!cfgPara_.coldExtList.empty()) {
        HMFS_INFO("Add new cold file extension list");
    }
    if (!cfgPara_.hotExtList.empty()) {
        HMFS_INFO("Add new hot file extension list");
    }

    if (!cfgPara_.volume.empty()) {
        HMFS_INFO("Volume label = %s", cfgPara_.volume.c_str());
    }

    HMFS_INFO("Trim is %s", cfgPara_.trim ? "enabled": "disabled");

    if (cfgPara_.defaultOptionSet == DEFAULT_OPTION_SET_HARMONY) {
        HMFS_INFO("Set conf for harmonyos");
    }

    if (cfgPara_.features & HMFS_FEATURE_CASEFOLD) {
        std::string str;
        if (!EncodingValueToStr(cfgPara_.sEncoding, str)) {
            HMFS_INFO("Enable %s with casefolding", str.c_str());
        }
    }

    if (cfgPara_.features & HMFS_FEATURE_PRJQUOTA) {
        HMFS_INFO("Enable Project quota");
    }

    if (cfgPara_.features & HMFS_FEATURE_COMPRESSION) {
        HMFS_INFO("Enable Compression");
    }
}

} // namespace Hmfs
} // namespace OHOS